__author__ = 'Denis Zorinets'

from numpy import zeros
from numpy import delete
from numpy.linalg import slogdet
from random import random
from math import exp

class Net:
    A=[]
    L=[]
    R=[]
    S=[]
    size = 0


    def __init__(self,size):
        self.size = size
    def GenER(self,p):
        self.A = zeros((self.size,self.size))
        for i in range(0,self.size):
            for j in range(i+1,self.size):
                if random()<p:
                    self.A[i][j] = self.A[j][i] = 1
                else:
                    self.A[i][j] = self.A[j][i] = 0

    def GenSW_Mk1(self,p,rank):
        self.GenER(p)
        for r in range(1,rank+1):
            for i in range(0,self.size-r):
                self.A[i][i+r] = self.A[i+r][i] = 1

    def GenSW_Mk2(self,p,rank):
        self.A = zeros((self.size,self.size))

        for r in range(1,rank+1):
            for i in range(0,self.size-r):
                if random()<p:
                    for j in range(i+1,self.size):
                        if random()<p:
                            self.A[i][j] = self.A[j][i]=1
                else:
                    self.A[i][i+r] = self.A[i+r][i] = 1

    def GenSF(self, m=1, m0=2):
        self.A = zeros((self.size,self.size))
        for i in range(0,m0):
            for j in range(i+1,m0):
                self.A[i][j] =self.A[j][i]= 1
        degrees = self.A.sum(1)
        sumDegree = degrees.sum()
        for k in range(m0, self.size):
            connected = []
            totalDegree = sumDegree
            for _ in range(m):
                rnd = random() * totalDegree
                curDegree = 0
                for i in range(k):
                    if i not in connected:
                        curDegree += degrees[i]
                        if rnd < curDegree:
                            connected.append(i)
                            totalDegree -= degrees[i]
                            degrees[i] += 1
                            self.A[i, k] = self.A[k, i] = 1
                            break
            degrees[k] += m
            sumDegree += 2 * m

    def CalcL(self):
        self.L = zeros((self.size,self.size))
        for i in range(0,self.size):
            S=0
            for j in range(0,self.size):
               S+=self.A[i][j]
               self.L[i][j] = - self.A[i][j]
            self.L[i][i] = S

    def Repair(self):
        flag = True
        while(flag):
            flag = False
            for i in range(0,self.size):
                if self.L[i][i]==0:
                    flag = True
                    self.L = delete(self.L,i,0)
                    self.L = delete(self.L,i,1)
                    self.A = delete(self.A,i,0)
                    self.A = delete(self.A,i,1)
                    self.size-=1
                    break

    def GetR(self,i,j):
        TMP = self.L.copy()
        if TMP.__len__()>2:
            TMP = delete(TMP,i,0)
            TMP = delete(TMP,i,1)

            (sign_up, logdet_up) = slogdet(TMP)
            if i>j:
                TMP = delete(TMP,j,0)
                TMP = delete(TMP,j,1)
            else:
                TMP = delete(TMP,j-1,0)
                TMP = delete(TMP,j-1,1)
            (sign_down, logdet_down) = slogdet(TMP)
            if(sign_down!=0):
                return sign_up/sign_down * (exp(logdet_up-logdet_down))
            else:
                return -1
        else:
            return 1

    def CalcR(self):
        self.R = zeros((self.size,self.size))
        for i in range(0,self.size):
            for j in range(i+1,self.size):
                if(self.A[i][j]==0):
                    self.R[i][j]= self.R[j][i] = self.GetR(i,j)
                else:
                    self.R[i][j] = self.R[j][i] = -1

    def CalcS(self):
        self.S = zeros((self.size,self.size))
        for i in range(0,self.size):
            for j in range(i+1,self.size):
                    self.S[i][j]= self.S[j][i] = self.R[i][j]

    def FindMinS(self):
        Min = 0
        for i in range(0,self.size):
            for j in range(i+1,self.size):
                if self.S[i][j]>0:
                    Min = self.S[i][j]
                    break
        for i in range(0,self.size):
            for j in range(i+1,self.size):
                if Min > self.S[i][j] and self.S[i][j]>0:
                    Min = self.S[i][j]
        return Min

    def FindMaxS(self):
        return self.S.max()

    def FindHidden(self,S):
        Num = 0
        for i in range(0,self.size):
            for j in range(i+1,self.size):
                if self.S[i][j]>=S and self.S[i][j]>0:
                    Num+=1
        return Num

    def CalcAll_ER(self,p):
        while True:
            while True:
                size_first = self.size
                self.GenER(p)
                self.CalcL()
                self.Repair()
                if(self.size < size_first-2):
                    self.size = size_first
                else:
                    break
            self.CalcR()
            self.CalcS()
            if self.FindMaxS()>0:
                break

    def CalcAll_SW_Mk1(self,p,rank):
        while True:
            while True:
                size_first = self.size
                self.GenSW_Mk1(p,rank)
                self.CalcL()
                self.Repair()
                if(self.size < size_first-2):
                    self.size = size_first
                else:
                    break
            self.CalcR()
            self.CalcS()
            if self.FindMaxS()>0:
                break

    def CalcAll_SW_Mk2(self,p,rank):
        while True:
            while True:
                size_first = self.size
                self.GenSW_Mk2(p,rank)
                self.CalcL()
                self.Repair()
                if(self.size < size_first-2):
                    self.size = size_first
                else:
                    break
            self.CalcR()
            self.CalcS()
            if self.FindMaxS()>0:
                break

    def CalcAll_SF(self,m,m0):
        while True:
            while True:
                size_first = self.size
                self.GenSF(m,m0)
                self.CalcL()
                self.Repair()
                if(self.size < size_first-2):
                    self.size = size_first
                else:
                    break
            self.CalcR()
            self.CalcS()
            if self.FindMaxS()>0:
                break

